contents
러스트(Rust)는 안전성, 속도, 동시성에 초점을 맞춘 현대적인 시스템 프로그래밍 언어입니다. 문법은 명시적이고 표현력이 풍부하게 설계되었으며, 컴파일러는 일반적인 버그를 예방하는 데 도움을 주는 파트너 역할을 합니다.
기본 문법 & "Hello, World!"
모든 실행 가능한 러스트 프로그램의 진입점은 main 함수입니다. 가장 고전적인 "Hello, World!" 예제는 다음과 같습니다.
fn main() {
println!("Hello, world!");
}
fn main() { ... }: 인자가 없고 아무것도 반환하지 않는main이라는 이름의 함수를 선언합니다.println!: 함수가 아닌 매크로입니다 (느낌표!로 표시됨). 매크로는 다른 코드를 작성하는 코드(메타프로그래밍)를 작성하는 방법입니다.println!은 콘솔에 텍스트를 출력하는 매크로입니다."Hello, world!": 문자열 리터럴입니다.;: 러스트에서는 문장의 끝에 세미콜론을 붙입니다.- 코드 실행: 일반적으로 러스트의 내장 패키지 관리자이자 빌드 도구인 Cargo를 사용하여
cargo run명령으로 코드를 실행합니다.
변수와 데이터 타입
러스트의 타입 시스템은 정적이며 강타입입니다. 즉, 모든 변수는 컴파일 시점에 알려진 타입을 가져야 합니다.
변수
- 기본적으로 불변(Immutable): 이것이 핵심적인 안전 기능입니다. 일단 변수에 값이 할당되면 그 값을 변경할 수 없습니다.
let x = 5;
// x = 6; // 컴파일 시점에 오류가 발생합니다!
- 가변 변수(Mutable): 변수를 변경 가능하게 만들려면
mut키워드를 사용해야 합니다. 이는 값을 변경하려는 의도를 명시적으로 만듭니다.
let mut y = 5;
y = 6; // 이것은 괜찮습니다.
- 타입 추론: 컴파일러는 보통 변수의 타입을 추론할 수 있으므로 항상 타입을 명시할 필요는 없습니다.
let is_active = true; // 컴파일러는 이 변수가 bool 타입이라고 추론합니다.
let pi: f64 = 3.14159; // 명시적으로 타입을 지정할 수도 있습니다.
일반적인 데이터 타입
-
스칼라 타입 (단일 값을 나타냄):
- 정수:
i32(기본값),u64,i8등 (i는 부호 있는 정수,u는 부호 없는 정수). - 부동 소수점 숫자:
f64(기본값),f32. - 불리언:
bool(true또는false). - 문자:
char(단일 유니코드 스칼라 값을 나타냄, 예:'a','🚀').
- 정수:
-
컴파운드 타입 (여러 값을 그룹화):
- 튜플: 고정된 크기의, 서로 다른 타입의 값들을 모은 컬렉션입니다.
let tup: (i32, f64, u8) = (500, 6.4, 1);
let first_value = tup.0; // 인덱스로 접근
- 배열: 고정된 크기의, 같은 타입의 값들을 모은 컬렉션입니다.
let a = [1, 2, 3, 4, 5];
let first_element = a[0]; // 인덱스로 접근
함수
함수는 fn 키워드로 정의됩니다. 러스트는 함수와 변수 이름에 snake_case를 사용합니다.
// 파라미터가 있는 함수
fn print_sum(a: i32, b: i32) {
println!("합계는: {}", a + b);
}
// 반환 값이 있는 함수
fn add_five(x: i32) -> i32 {
x + 5
}
- 파라미터: 각 파라미터의 타입을 선언해야 합니다:
이름: 타입. - 반환 값: 화살표
->뒤에 반환 타입을 명시합니다. - 암시적 반환: 이것이 핵심 기능입니다. 함수 블록의 마지막 표현식은 자동으로 반환됩니다.
x + 5뒤에 세미콜론이 없는 것을 주목하세요. 세미콜론을 추가하면 문장이 되어 반환되지 않습니다.
Struct와 Impl (러스트의 "클래스") 🧱
러스트는 자바나 파이썬과 같은 언어에서 말하는 "클래스"를 가지고 있지 않습니다. 대신, 데이터와 동작을 분리합니다.
struct(구조체): 관련된 값들을 그룹화하여 사용자 정의 데이터 타입을 정의하는 데 사용됩니다. 객체의 "필드"나 "속성"을 정의하는 곳입니다.impl(구현 블록):struct와 관련된 "메서드"(동작)를 정의하는 데 사용됩니다.
// 데이터 구조 정의
struct Rectangle {
width: u32,
height: u32,
}
// Rectangle 구조체에 대한 동작 정의
impl Rectangle {
// 구조체 인스턴스에 대한 불변 참조를 받는 메서드
fn area(&self) -> u32 {
self.width * self.height
}
// 정적 메서드처럼 작동하는 "연관 함수"
fn square(size: u32) -> Self {
Self { width: size, height: size }
}
}
// 사용 방법
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("넓이는 {}", rect1.area());
let sq = Rectangle::square(25);
println!("정사각형의 넓이는 {}", sq.area());
}
&self: 메서드가 호출되는 구조체 인스턴스를 나타내는 파라미터입니다. 다른 언어의this나self와 유사합니다.
Enum과 패턴 매칭 🛤️
Enum(열거형) 은 가능한 값들을 나열하여 타입을 정의할 수 있게 해줍니다. 러스트에서 Enum은 매우 강력합니다.
match 는 switch 문을 초강력하게 만든 것과 같은 제어 흐름 구문입니다. 값을 일련의 패턴과 비교하고 어떤 패턴이 일치하는지에 따라 코드를 실행할 수 있게 해줍니다. match는 철저해서(exhaustive), 컴파일러가 모든 가능한 경우를 처리하도록 강제합니다.
enum Direction {
Up,
Down,
Left,
Right,
}
fn move_avatar(direction: Direction) {
match direction {
Direction::Up => println!("위로 이동!"),
Direction::Down => println!("아래로 이동!"),
Direction::Left => println!("왼쪽으로 이동!"),
Direction::Right => println!("오른쪽으로 이동!"),
}
}
fn main() {
move_avatar(Direction::Left);
}
컨벤션 및 문자열에 대한 참고 사항
이름 컨벤션
snake_case: 변수, 함수 이름, 모듈 이름에 사용됩니다 (예:let my_variable).PascalCase:struct,enum,trait와 같은 타입에 사용됩니다 (예:struct MyStruct).SCREAMING_SNAKE_CASE: 상수에 사용됩니다 (예:const MAX_POINTS: u32 = 100_000;).rustfmt: 러스트에는 이러한 컨벤션을 따르도록 코드를 자동으로 포맷해주는 공식 도구rustfmt가 있습니다.
String vs. &str에 대한 간단한 참고
이는 신규 사용자들이 흔히 헷갈리는 부분이며 러스트의 독특한 소유권 시스템을 잘 보여줍니다.
String: 힙에 저장되는, 소유권을 가지며, 크기가 커질 수 있는, 변경 가능한 문자열입니다. 문자열 데이터를 소유하고 수정해야 할 때String을 사용합니다.&str(문자열 슬라이스): "빌려온", 불변의 문자열 뷰입니다. 이는 UTF-8 바이트 시퀀스를 가리키는 가벼운 포인터입니다. 대부분의 문자열 리터럴은&str타입입니다.
문자열 데이터를 받는 함수를 작성할 때는 파라미터로 &str을 받는 것이 관례입니다. 이는 String( &str로 빌릴 수 있음)과 문자열 리터럴을 모두 받을 수 있어 더 유연하기 때문입니다.
references